/*****************************************************************************
 * vfs_lfs.c
 *
 * Copyright (C) 2019 Jeasonvor <1101627719@qq.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ****************************************************************************/

/*****************************************************************************
 * Included Files
 ****************************************************************************/

#include "plum_api.h"
#include "thirdparty/littlefs/lfs.h"

/*****************************************************************************
 * Trace Definitions
 ****************************************************************************/
#define LOG_RUN_LEVEL LOG_LEVEL_DEBUG
#define LOG_MODULE    "vfs.lfs"
#include "thirdparty/log/log.h"

/*****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
#define VFS_LFS_MALLOC malloc
#define VFS_LFS_FREE   free

#define VFS_LFS_NODE_ENTRY(node, type, member) \
    (type *)((char *)node - ((size_t) & ((type *)0)->member))
/*****************************************************************************
 * Private Types
 ****************************************************************************/

typedef struct {
    plum_u32         fd;   /** plum 文件句柄 */
    lfs_file_t       file; /** lfs文件系统句柄 */
    struct list_head list;
} vfs_map_t;

/**
 * @brief: LFS文件系统描述句柄
 *
 * @note:   支持挂在多个内存块
 */
typedef struct {
    plum_s32         point[16]; /** 挂载点 **/
    lfs_t *          vfs;       /** 文件系统描述符 */
    fs_flash_t *     vflash;    /** flash 操作值*/
    vfs_map_t *      map;       /** 隐射表 */
    struct list_head list;      /** 内核链表 */
} vfs_hdl_t;

/*****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/*****************************************************************************
 * Private Data
 ****************************************************************************/

PLUM_PRIVATE vfs_hdl_t lfs_head; /** 文件系统头节点，支持挂载多个lfs节点 */
PLUM_PRIVATE vfs_hdl_t *active;        /** 当前活跃的文件系统 */
PLUM_PRIVATE plum_u32   vfs_fd_conter; /** fd计数器 **/

/*****************************************************************************
 * Public Data
 ****************************************************************************/

/*****************************************************************************
 * Private Functions
 ****************************************************************************/

PLUM_PRIVATE
vfs_hdl_t *vfs_lfs_node_apply(PLUM_CONST plum_s8 *point, fs_flash_t *flash)
{
    vfs_hdl_t *node = plum_null;

    do {
        if (lfs_head.list.next) {
            INIT_LIST_HEAD(&lfs_head.list);
        }
        node = VFS_LFS_MALLOC(sizeof(vfs_hdl_t));
        if (!node) {
            LOG_E("apply node err");
            break;
        }
        memset(node, 0, sizeof(vfs_hdl_t));
        snprintf((char *)node->point, sizeof(node->point), "%s", point);

        node->vflash = VFS_LFS_MALLOC(sizeof(fs_flash_t));
        if (!node->vflash) {
            LOG_E("apply vflash err");
            goto err;
        }
        memset(node->vflash, 0, sizeof(fs_flash_t));
        memcpy(node->vflash, flash, sizeof(fs_flash_t));

        node->vfs = VFS_LFS_MALLOC(sizeof(lfs_t));
        if (!node->vfs) {
            LOG_E("apply vfs err");
            goto err;
        }
        memset(node->vfs, 0, sizeof(lfs_t));
    } while (0);

    return (node);

err:
    if (node->vfs) {
        VFS_LFS_FREE(node->vfs);
    }
    if (node->vflash) {
        VFS_LFS_FREE(node->vflash);
    }
    if (node) {
        VFS_LFS_FREE(node);
    }

    return (plum_null);
}

PLUM_PRIVATE
vfs_hdl_t *vfs_lfs_node_find(PLUM_CONST plum_s8 *path)
{
    vfs_hdl_t *node = plum_null;

    struct list_head *pos = plum_null;

    list_for_each(pos, &lfs_head.list)
    {
        node = list_entry(pos, vfs_hdl_t, list);
        if (strncmp((char *)node->point, (char *)path,
                    strlen((char *)node->point)) == 0) {
            break;
        }
    }

    return (node);
}

PLUM_PRIVATE
plum_void vfs_lfs_node_insert(vfs_hdl_t *node)
{
    list_add_tail(&node->list, &lfs_head.list);
}

PLUM_PRIVATE
vfs_hdl_t *vfs_lfs_node_find_by_fd(plum_u32 fd)
{
    vfs_hdl_t *outside = plum_null;
    vfs_map_t *inside  = plum_null;

    struct list_head *pos1 = plum_null;
    struct list_head *pos2 = plum_null;

    list_for_each(pos1, &lfs_head.list)
    {
        outside = list_entry(pos1, vfs_hdl_t, list);
        list_for_each(pos2, &outside->map->list)
        {
            inside = list_entry(pos2, vfs_map_t, list);
            if (inside->fd == fd) {
                return (outside);
            }
        }
    }

    return (plum_null);
}

PLUM_PRIVATE
plum_void vfs_lfs_node_del(vfs_hdl_t *node)
{
    list_del(&node->list);
}

PLUM_PRIVATE
vfs_map_t *vfs_lfs_map_apply(plum_u32 fd, lfs_file_t *file)
{
    vfs_map_t *map = PLUM_ECODE_OK;

    do {
        map = VFS_LFS_MALLOC(sizeof(vfs_map_t));
        if (!map) {
            LOG_E("vfs malloc map err");
            break;
        }
        memset(map, 0, sizeof(vfs_map_t));
        INIT_LIST_HEAD(&map->list);

        map->fd = fd;
        memcpy(&map->file, file, sizeof(lfs_file_t));
    } while (0);

    return (map);
}

PLUM_PRIVATE
vfs_map_t *vfs_lfs_map_find(plum_u32 fd)
{
    vfs_hdl_t *outside = plum_null;
    vfs_map_t *inside  = plum_null;

    struct list_head *pos1 = plum_null;
    struct list_head *pos2 = plum_null;

    list_for_each(pos1, &lfs_head.list)
    {
        outside = list_entry(pos1, vfs_hdl_t, list);
        list_for_each(pos2, &outside->map->list)
        {
            inside = list_entry(pos2, vfs_map_t, list);
            if (inside->fd == fd) {
                return (inside);
            }
        }
    }

    return (plum_null);
}

PLUM_PRIVATE
plum_void vfs_lfs_map_insert(vfs_hdl_t *node, vfs_map_t *map)
{
    if (node->map == plum_null) {
        node->map = map;
    }
    else {
        list_add_tail(&map->list, &node->map->list);
    }
}

PLUM_PRIVATE
plum_void vfs_lfs_map_del(vfs_hdl_t *node, vfs_map_t *map)
{
    if (node->map->list.next != plum_null) {
        list_del(&map->list);
    }
    VFS_LFS_FREE(map);
}

PLUM_PRIVATE
plum_s32 vfs_down_read(PLUM_CONST struct lfs_config *c, lfs_block_t block,
                       lfs_off_t off, void *buffer, lfs_size_t size)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (active->vflash->flash_read == plum_null) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("the file system not mount,read is invalid");
            break;
        }

        plum_u32 addr =
            block * c->block_size + off + active->vflash->flash_start_addr;

        rc = active->vflash->flash_read(addr, (plum_u8 *)buffer, size);
        if (rc == 0) {
            LOG_E("read err,addr:0x%x,size:%d", addr, size);
            rc = PLUM_ECODE_EIO;
            break;
        }

        LOG_I("read %d bytes at [0x%x]", rc, addr);
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 vfs_down_write(PLUM_CONST struct lfs_config *c, lfs_block_t block,
                        lfs_off_t off, PLUM_CONST void *buffer, lfs_size_t size)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (active->vflash->flash_write == plum_null) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("vfs not init,rc : %d", rc);
            break;
            ;
        }

        plum_u32 addr =
            block * c->block_size + off + active->vflash->flash_start_addr;

        rc = active->vflash->flash_write(addr, (plum_u8 *)buffer, size);
        if (rc == 0) {
            rc = PLUM_ECODE_EIO;
            LOG_E("write err,addr:0x%x,size:%d", addr, size);
            break;
            ;
        }
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 vfs_down_erase(PLUM_CONST struct lfs_config *c, lfs_block_t block)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (active->vflash->flash_erase == plum_null) {
            LOG_E("the flash erase api not register");
            rc = PLUM_ECODE_INVALID;
            break;
        }

        plum_u32 addr =
            block * c->block_size + active->vflash->flash_start_addr;

        LOG_I("ready to erase at [%d],size:%d", addr, c->block_size);
        rc = active->vflash->flash_erase(addr, c->block_size);
        if (rc == 0) {
            LOG_E("erase err,addr:0x%x", addr);
            rc = PLUM_ECODE_EIO;
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 vfs_down_sync(PLUM_CONST struct lfs_config *c)
{
    plum_s32 rc = PLUM_ECODE_OK;
    (void)c;

    LOG_I("this is down sync");

    return (rc);
}

PLUM_PRIVATE
plum_s32 vfs_up_read(plum_u32 fd, plum_u8 *buf, plum_u32 size)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        vfs_map_t *map = vfs_lfs_map_find(fd);
        if (!map) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("the fd id invalid");
            break;
        }

        vfs_hdl_t *node = vfs_lfs_node_find_by_fd(fd);
        if (!node) {
            rc = PLUM_ECODE_EPARA;
            LOG_E("the fd is invalid");
            break;
        }

        rc = lfs_file_read(node->vfs, &map->file, buf, size);
        if (rc <= 0) {
            LOG_E("lfs_file_read err,rc: %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 vfs_up_write(plum_s32 fd, plum_u8 *buf, plum_u32 size)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        vfs_map_t *map = vfs_lfs_map_find(fd);
        if (!map) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("the fd id invalid");
            break;
        }

        vfs_hdl_t *node = vfs_lfs_node_find_by_fd(fd);
        if (!node) {
            rc = PLUM_ECODE_EPARA;
            LOG_E("the fd is invalid");
            break;
        }

        rc = lfs_file_write(node->vfs, &map->file, buf, size);
        if (rc <= 0) {
            LOG_E("lfs_file_read err,rc: %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 vfs_up_open(lfs_t *lfs, PLUM_CONST plum_s8 *path, plum_s32 flags)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        plum_s32 oflags = 0;

        if (flags & VFS_O_RDONLY) {
            oflags |= LFS_O_RDONLY;
        }

        if (flags & VFS_O_WRONLY) {
            oflags |= LFS_O_WRONLY;
        }

        if (flags & VFS_O_RDWR) {
            oflags |= LFS_O_RDWR;
        }

        if (flags & VFS_O_CREATE) {
            oflags |= LFS_O_CREAT;
        }

        if (flags & VFS_O_EXCL) {
            oflags |= LFS_O_EXCL;
        }

        if (flags & VFS_O_APPEND) {
            oflags |= LFS_O_APPEND;
        }

        if (flags & VFS_O_TRUNC) {
            oflags |= LFS_O_TRUNC;
        }

        vfs_hdl_t *node = vfs_lfs_node_find(path);
        if (!node) {
            rc = PLUM_ECODE_EPARA;
            LOG_E("the fd is invalid");
            break;
        }

        lfs_file_t file = {0};
        rc              = lfs_file_open(lfs, &file, (char *)path, oflags);
        if (rc) {
            LOG_E("open [%s] err,rc:%d", path, rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        vfs_map_t *map = vfs_lfs_map_apply(++vfs_fd_conter, &file);
        if (rc) {
            rc = PLUM_ECODE_EMEM;
            LOG_E("vfs_lfs_map_apply err,rc : %d", rc);
            lfs_file_close(lfs, &file);
        }

        vfs_lfs_map_insert(node, map);
        rc = map->fd;
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 vfs_up_close(plum_s32 fd)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        vfs_map_t *map = vfs_lfs_map_find(fd);
        if (!map) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("not find the file discribe symbol,rc : %d", rc);
            break;
        }

        vfs_hdl_t *pos;
        vfs_hdl_t *node = VFS_LFS_NODE_ENTRY(pos, vfs_hdl_t, map);
        lfs_file_close(node->vfs, &map->file);

        vfs_lfs_map_del(node, map);
        PLUM_UNUSED(pos);
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 vfs_up_seek(plum_u32 fd, plum_u32 offset, plum_u32 whence)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        vfs_map_t *map = vfs_lfs_map_find(fd);
        if (!map) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("vfs_lfs_map_find err,rc: %d", rc);
            break;
        }

        vfs_hdl_t *pos;
        vfs_hdl_t *node = VFS_LFS_NODE_ENTRY(pos, vfs_hdl_t, map);

        rc = lfs_file_seek(node->vfs, &map->file, offset, whence);
        if (rc <= 0) {
            LOG_E("lfs_file_seek err,rc: %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }
        PLUM_UNUSED(pos);
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 vfs_up_offset(plum_u32 fd)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        vfs_map_t *map = vfs_lfs_map_find(fd);
        if (!map) {
            LOG_E("vfs_lfs_map_find err,rc: %d", rc);
            break;
        }

        rc = map->file.pos;
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 vfs_up_sync(plum_s32 fd)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        vfs_map_t *map = vfs_lfs_map_find(fd);
        if (!map) {
            LOG_E("vfs_lfs_map_find err,rc: %d", rc);
            break;
        }

        vfs_hdl_t *pos;
        vfs_hdl_t *node = VFS_LFS_NODE_ENTRY(pos, vfs_hdl_t, map);

        rc = lfs_file_sync(node->vfs, &map->file);
        if (rc) {
            LOG_E("lfs_file_sync err,rc: %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 vfs_up_readdir(PLUM_CONST plum_s8 *path)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!path) {
            rc = PLUM_ECODE_EMEM;
            LOG_E("direct path is err");
            break;
        }

        vfs_hdl_t *node = vfs_lfs_node_find(path);
        if (!node) {
            LOG_E("not find the directory: %s", path);
            rc = PLUM_ECODE_EPARA;
            break;
        }

        lfs_dir_t dir;
        rc = lfs_dir_open(node->vfs, &dir, (const char *)path);
        if (rc) {
            LOG_E("lfs_dir_open err,rc: %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        struct lfs_info info;
        rc = lfs_dir_read(node->vfs, &dir, &info);
        if (rc) {
            LOG_E("lfs_dir_read err,rc: %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        LOG_I("read dir: type:%d, size:%d,name:%s", info.type, info.size,
              info.type);
    } while (0);

    return (rc);
}

/*****************************************************************************
 * Public Functions
 ****************************************************************************/

PLUM_PUBLIC
plum_s32 plum_cal_vfs_mount(PLUM_CONST plum_s8 *path, fs_flash_t *flash)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!path || !flash) {
            rc = PLUM_ECODE_EPARA;
            LOG_E("the paramter is null");
            break;
        }

        if (path[0]) {
            rc = PLUM_ECODE_INVALID;
            LOG_E(
                "this littlefs file system mount poplum_s32 must start with "
                "'\\'");
            break;
        }

        vfs_hdl_t *node = vfs_lfs_node_apply(path, flash);
        if (!node) {
            rc = PLUM_ECODE_EMEM;
            LOG_E("vfs_lfs_apply_node err,rc : %d", rc);
            break;
        }

        struct lfs_config cfg = {0};
        cfg.read              = vfs_down_read;
        cfg.prog              = vfs_down_write;
        cfg.erase             = vfs_down_erase;
        cfg.sync              = vfs_down_sync;
        cfg.read_size         = flash->flash_read_size;
        cfg.prog_size         = flash->flash_prog_size;
        cfg.block_size        = flash->flash_block_size;
        cfg.block_count       = flash->flash_block_count;
        cfg.cache_size        = VFS_CACHE_SIZE;
        cfg.lookahead_size    = 16;
        cfg.block_cycles      = -1;
        rc                    = lfs_mount(node->vfs, &cfg);
        if (rc) {
            LOG_E("lfs_mount err,rc: %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        vfs_lfs_node_insert(node);
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 vfs_unmount(PLUM_CONST plum_s8 *path)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!path) {
            LOG_E("this mount poplum_s32er is null");
            rc = PLUM_ECODE_EPARA;
            break;
        }

        vfs_hdl_t *node = vfs_lfs_node_find(path);
        if (!node) {
            LOG_E("not find the mount poplum_s32");
            rc = PLUM_ECODE_EPARA;
            break;
        }

        rc = lfs_unmount(node->vfs);
        if (rc) {
            LOG_E("lfs_unmount err,rc: %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        vfs_lfs_node_del(node);
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_vfs_open(plum_s8 *path, plum_u32 mode)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!path) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("this path is invalid");
            break;
        }

        vfs_hdl_t *node = vfs_lfs_node_find(path);
        if (!node) {
            LOG_E("not find the file");
            rc = PLUM_ECODE_EPARA;
            break;
        }

        rc = vfs_up_open(node->vfs, path, mode);
        if (rc) {
            LOG_E("vfs_up_open err,rc: %d", rc);
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_vfs_close(plum_u32 fd)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!fd) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("this fd is invalid,rc: %d", rc);
            break;
        }

        rc = vfs_up_close(fd);
        if (rc) {
            LOG_E("vfs_up_close err,rc: %d", rc);
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_vfs_read(plum_u32 fd, plum_u8 *rData, plum_u32 size)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!fd || !rData || !size) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("the parameter is invalid");
            break;
        }

        rc = vfs_up_read(fd, rData, size);
        if (rc) {
            LOG_E("vfs_up_read err,rc: %d", rc);
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_vfs_write(plum_u32 fd, plum_u8 *wData, plum_u32 size)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!fd || !wData || !size) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("the parameter is invalid");
            break;
        }

        rc = vfs_up_write(fd, wData, size);
        if (rc) {
            LOG_E("vfs_up_write err,rc: %d", rc);
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_vfs_seek(plum_u32 fd, plum_u32 offset,
                           plum_vfs_seek_whence_t whence)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!fd) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("the parameter is invalid");
            break;
        }

        plum_s32 flag;
        switch (whence) {
            case PLUM_VFS_SEEK_SET:
                flag = VFS_SEEK_SET;
                break;
            case PLUM_VFS_SEEK_END:
                flag = VFS_SEEK_END;
                break;
            case PLUM_VFS_SEEK_CUR:
                flag = VFS_SEEK_CUR;
                break;
        }

        rc = vfs_up_seek(fd, offset, flag);
        if (rc) {
            LOG_E("vfs_up_seek err,rc: %d", rc);
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_vfs_offset(plum_u32 fd)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!fd) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("the parameter is invalid");
            break;
        }

        rc = vfs_up_offset(fd);
        if (rc) {
            LOG_E("vfs_up_offset err,rc: %d", rc);
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_vfs_sync(plum_u32 fd)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!fd) {
            rc = PLUM_ECODE_INVALID;
            LOG_E("the parameter is invalid");
            break;
        }

        rc = vfs_up_sync(fd);
        if (rc) {
            LOG_E("vfs_up_sync err,rc: %d", rc);
            break;
        }
    } while (0);

    return (rc);
}

plum_s32 plum_cal_vfs_readdir(PLUM_CONST plum_s8 *path)
{
    return vfs_up_readdir(path);
}
/****************************************************************************/
/*                                                                          */
/*  End of file.                                                            */
/*                                                                          */
/****************************************************************************/
